package com.mock.components;

import cn.hutool.core.collection.ConcurrentHashSet;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @decription: Tomcat配置
 * @date: 2023/04/19 0019 16:21:23
 * @author: caijianwei@xinpayroll.com
 * @since: v1.0.0
 **/
@Slf4j
@ConfigurationProperties(prefix = "mock.server.custom")
public class TomcatConfig {

    @Getter
    @Setter
    private Integer portNumbers = 2;

    // 最多生成的端口号个数，个数太多容易冲突
    @Getter
    @Setter
    private int portMax = 10;

    // 记录所有的端口号
    private static final Set<Integer> unUsedPorts = new ConcurrentHashSet<>(100);

    // 记录已使用的端口号，避免冲突
    private final static Map<String, Integer> haveUsedPort = new ConcurrentHashMap<>(100);


    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        // 配置支持多端口号
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addAdditionalTomcatConnectors(connectors());
        return factory;
    }

    private Connector[] connectors() {
        String protocal = "org.apache.coyote.http11.Http11NioProtocol";
        List<Connector> connectorList = new ArrayList<>(portNumbers);
        for (int i = 0; i < portNumbers; i++) {
            Connector connector = new Connector(protocal);
            connector.setScheme("http");
            connector.setPort(randomPort());
            connectorList.add(connector);
        }
        return connectorList.toArray(new Connector[0]);
    }


    /**
     * 随机生成端口号
     *
     * @return
     */
    private int randomPort() {
        if (portMax <= unUsedPorts.size()) {
            throw new RuntimeException("端口生成已超过上限，请下线其他服务后重试");
        }
        int port = RandomUtils.nextInt(10000, 20000);
        boolean contains = unUsedPorts.add(port);
        if (!contains) {
            randomPort();
        }
        return port;
    }


    /**
     * 获取所有端口
     *
     * @param serviceId
     * @return
     */
    public static int getPort(String serviceId) {
        if (StringUtils.isBlank(serviceId)) {
            throw new RuntimeException("serviceId不能为空");
        }

        if (CollectionUtils.isEmpty(unUsedPorts)) {
            log.info("端口对应已使用的服务为：{}", JSON.toJSONString(haveUsedPort));
            throw new RuntimeException("端口号已被用完，请释放其他服务或者修改端口生成的数量后重启");
        }

        int usedPort = 0;

        synchronized (unUsedPorts) {
            // 获取一个未使用的端口
            for (Integer port : unUsedPorts) {
                haveUsedPort.put(serviceId, port);
                usedPort = port;
                break;
            }
            // 删除已使用的端口
            unUsedPorts.remove(usedPort);
        }


        return usedPort;
    }

    /**
     * 释放端口
     *
     * @param serviceId
     * @return
     */
    public static void releasePort(String serviceId) {
        if (StringUtils.isBlank(serviceId)) {
            throw new RuntimeException("serviceId不能为空");
        }

        // 判断服务是否已使用过，未使用则忽略
        Integer port = haveUsedPort.get(serviceId);
        if (port == null) {
            log.info("serviceId未注册，{}", serviceId);
            return;
        }

        synchronized (unUsedPorts) {
            // 存储已释放的端口
            boolean addResult = unUsedPorts.add(port);
            if (addResult) {
                // 删除对应的已使用端口
                haveUsedPort.remove(serviceId);
            }

        }
        log.info("端口释放成功，serviceId：{}， port：{}", serviceId, port);
    }
}
